﻿
Imports System.IO

Public Module file_directory

    Private Delegate Function listDelegate(ByVal src As String) As String()

    Private Function listImpl(ByVal src As String, ByVal list As listDelegate) As String()
        If Not Directory.Exists(src) Then
            Return Nothing
        Else
            Try
                Return list(src)
            Catch
                Return Nothing
            End Try
        End If
    End Function

    Public Function listfiles(ByVal src As String) As String()
        Return listImpl(src, AddressOf Directory.GetFiles)
    End Function

    Public Function listdirectories(ByVal src As String) As String()
        Return listImpl(src, AddressOf Directory.GetDirectories)
    End Function

    Public Function createDirectory(ByVal src As String, Optional ByVal retrytimes As Int32 = 256) As Boolean
        Dim i As Int32
        If Directory.Exists(src) Then
            Return True
        Else
            For i = 1 To retrytimes
                Try
                    Directory.CreateDirectory(src)
                    Exit For
                Catch
                End Try
            Next
            If i > retrytimes Then
                raiseError("cannot create directory " + src + "." _
                            , errorHandle.errorType.exclamation)
                Return False
            Else
                Return True
            End If
        End If
    End Function

    Public Function copyDirectory(ByVal src As String, ByVal dest As String _
                                , Optional ByVal ignore_empty_directory As Boolean = False _
                                , Optional ByVal retryTimes As Int32 = 256 _
                                , Optional ByVal overwrite As Boolean = True) As Boolean
        If Not Directory.Exists(src) Then
            raiseError("source directory " + src + " not existing, copy failed.", errorHandle.errorType.exclamation)
            Return False
        End If

        Dim files() As String = Nothing
        Dim dirs() As String = Nothing
        Dim rtn As Boolean = True
        files = listfiles(src)
        dirs = listdirectories(src)
        If isemptyarray(dirs) AndAlso isemptyarray(files) AndAlso ignore_empty_directory Then
            Return True
        End If

        If Not createDirectory(dest) Then
            raiseError("cannot create directory " + dest + ", copy failed.", errorHandle.errorType.exclamation)
            Return False
        End If

        Dim i As Int32
        If Not isemptyarray(files) Then
            For i = 0 To files.Length() - 1
                Dim j As Int32
                For j = 1 To retryTimes
                    Try
                        File.Copy(files(i), Path.Combine(dest, Path.GetFileName(files(i))), overwrite)
                        Exit For
                    Catch
                        sleep(16)
                    End Try
                Next

                If j > retryTimes Then
                    raiseError("cannot copy file " + files(i) + " from " + src + " to " + dest _
                                , errorHandle.errorType.exclamation)
                    rtn = False
                End If
            Next
        End If

        If Not isemptyarray(dirs) Then
            For i = 0 To dirs.Length() - 1
                If Not copyDirectory(dirs(i), dest + strmid(dirs(i), strlen(src)) _
                                    , ignore_empty_directory, retryTimes, overwrite) Then
                    rtn = False
                End If
            Next
        End If

        Return rtn
    End Function

    Public Function deleteDirectory(ByVal src As String, Optional ByVal retryTimes As Int32 = 256) As Boolean
        If Not Directory.Exists(src) Then
            Return True
        Else
#If RETIRED Then
            Dim files() As String = Nothing
            Dim dirs() As String = Nothing
            Dim rtn As Boolean = True
            files = listfiles(src)
            dirs = listdirectories(src)
            Dim i As Int32
            Dim j As Int32
            If Not isemptyarray(files) Then
                For i = 0 To files.Length() - 1
                    For j = 1 To retryTimes
                        Try
                            File.Delete(files(i))
                            Exit For
                        Catch ex As Exception
                            If j = 1 Then
                                raiseError("cannot delete file " + files(i) + ", ex " + ex.Message + ", retrying...", _
                                           errorHandle.errorType.system)
                            End If
                            sleep(16)
                        End Try
                    Next
                    If j > retryTimes Then
                        raiseError("cannot delete file " + files(i) + ", ignore", errorHandle.errorType.exclamation)
                        rtn = False
                    End If
                Next
            End If
            If Not isemptyarray(dirs) Then
                For i = 0 To dirs.Length() - 1
                    If Not deleteDirectory(dirs(i), retryTimes) Then
                        rtn = False
                    End If
                Next
            End If

            If rtn Then
                For j = 1 To retryTimes
                    Try
                        Directory.Delete(src)
                        Exit For
                    Catch ex As Exception
                        If j = 1 Then
                            raiseError("cannot delete directory " + src + ", ex " + ex.Message + ", retrying...", _
                                        errorHandle.errorType.system)
                        End If
                        sleep(16)
                    End Try
                Next
                If j > retryTimes Then
                    raiseError("cannot delete directory " + src, errorHandle.errorType.exclamation)
                    rtn = False
                End If
            End If

            Return rtn
#End If
            Dim i As Int32
            For i = 1 To retryTimes
                Try
                    Directory.Delete(src, True)
                    Exit For
                Catch
                    sleep(16)
                End Try
            Next

            If i > retryTimes Then
                raiseError("cannot delete directory " + src, errorHandle.errorType.exclamation)
                Return False
            Else
                Return True
            End If
        End If
    End Function

    Private Function _driveRootDirSepIndex(ByVal src As String) As Int64
        If isdebugmode() Then
            assert(Path.IsPathRooted(src))
        End If

        Return strindexof(src, filesystem.directorySeperator)
    End Function

    Public Function driveRoot(ByVal src As String) As String
        src = fullpath(src)
        Dim index As Int64
        index = _driveRootDirSepIndex(src)
        If index = npos Then
            Return src
        Else
            Return strleft(src, index)
        End If
    End Function

    Public Function isDriveRoot(ByVal src As String) As Boolean
        src = fullpath(src)
        Dim index As Int64
        index = _driveRootDirSepIndex(src)
        'the first \ is at the end of src or no \ at all
        Return index = npos OrElse index = strlen(src) - 1
    End Function

    Public Function deleteEmptyDirectory(ByVal src As String, _
                                         Optional ByVal recursiveTimes As Int64 = maxInt64, _
                                         Optional ByVal retryTimes As Int32 = 256) As Boolean
        If Not Directory.Exists(src) Then
            Return True
        Else
            Dim i As Int64
            For i = 1 To recursiveTimes
                Dim j As Int32
                For j = 1 To retryTimes
                    Try
                        Directory.Delete(src)
                        Exit For
                    Catch
                        sleep(16)
                    End Try
                Next

                If j > retryTimes Then
                    Exit For
                End If
                src = parentpath(src)
                If isDriveRoot(src) Then
                    Exit For
                End If
            Next

            Return i > recursiveTimes
        End If
    End Function

    Public Function fullpath(ByVal src As String, _
                             Optional ByVal needmsg As Boolean = True) As String
        Try
            Dim dir As String = Nothing
            dir = Path.GetDirectoryName(src)
            If isemptystring(dir) Then
                dir = Environment.CurrentDirectory()
            End If
            Return combinepath(Path.GetFullPath(dir), Path.GetFileName(src))
        Catch ex As Exception
            If needmsg Then
                raiseError("cannot confirm whether IsPathRooted for " + src + _
                           ", ignore and return src, message " + ex.Message, errorHandle.errorType.warning)
            End If
            Return src
        End Try
    End Function

    Public Function getfullpath(ByVal src As String, Optional ByVal needmsg As Boolean = True) As String
        Return fullpath(src, needmsg)
    End Function

    Public Function getparentpath(ByVal src As String) As String
        Return parentpath(src)
    End Function

    Public Function parentpath(ByVal src As String) As String
        Dim index As Int32
        src = fullpath(src)
        index = src.LastIndexOf(filesystem.directorySeperator)
        If index <> npos Then
            Return strleft(src, index)
        Else
            Return src
        End If
    End Function

    Public Function thislevelname(ByVal src As String) As String
        Dim index As Int32
        src = fullpath(src)
        index = src.LastIndexOf(filesystem.directorySeperator)
        If index <> npos Then
            Return strmid(src, index + 1)
        Else
            Return src
        End If
    End Function

    Public Function directoryName(ByVal src As String) As String
        Return thislevelname(src)
    End Function

    Public Function getdirectoryName(ByVal src As String) As String
        Return directoryName(src)
    End Function

    Public Function filename(ByVal src As String) As String
        Return thislevelname(src)
    End Function

    Public Function getfilename(ByVal src As String) As String
        Return filename(src)
    End Function

    Public Function invalidPathChar(ByVal c As Char) As Boolean
        Return filesystem.invalidPathChars.IndexOf(c) <> npos
    End Function

    Public Function invalidFilenameChar(ByVal c As Char) As Boolean
        Return filesystem.invalidFilenameChars.IndexOf(c) <> npos
    End Function

    Public Function containsInvalidPathChar(ByVal s As String) As Boolean
        Dim i As Int64
        For i = 0 To strlen(s) - 1
            If invalidPathChar(s(i)) Then
                Return True
            End If
        Next

        Return False
    End Function

    Public Function containsInvalidFilenameChar(ByVal s As String) As Boolean
        Dim i As Int64
        For i = 0 To strlen(s) - 1
            If invalidFilenameChar(s(i)) Then
                Return True
            End If
        Next

        Return False
    End Function

    Public Function filenamesuffix(ByVal filename As String) As String
        Dim index As Int32
        index = filename.LastIndexOf(filesystem.suffixSeperator)
        If index = npos Then
            Return emptyString
        Else
            Return strmid(filename, index + 1)
        End If
    End Function

#If Not (PocketPC OrElse Smartphone) Then
    Public Function GetShortPathNameEx(ByVal longname As String, ByVal shortname As String) As Boolean
        Dim buff As Text.StringBuilder = New Text.StringBuilder(MAX_PATH)
        If GetShortPathName(longname, buff, buff.MaxCapacity) > 0 Then
            shortname = buff.ToString()
            Return True
        Else
            Return False
        End If
    End Function
#End If

    'cannot make s() as ParamArray, to avoid to make wrong call to this function
    Public Function combinepath(ByRef rst As String, ByVal s() As String, _
                                Optional ByVal needmsg As Boolean = True) As Boolean
        If Not s Is Nothing AndAlso s.Length() > 0 Then
            rst = s(0)
            Dim i As Int32
            For i = 1 To s.Length() - 1
                Try
                    rst = Path.Combine(rst, s(i))
                Catch ex As Exception
                    If needmsg Then
                        raiseError("cannot combine path " + rst + " with " + s(i) + _
                                   ", ex " + ex.Message + ", callstack " + ex.StackTrace, _
                                   errorHandle.errorType.exclamation)
                    End If
                    Return False
                End Try
            Next

            Return True
        Else
            Return False
        End If
    End Function

    Private Function combinepath(ByVal needmsg As Boolean, ByVal s() As String) As String
        Dim rst As String = Nothing
        If combinepath(rst, s, needmsg) Then
            Return rst
        Else
            Return Nothing
        End If
    End Function

    Public Function combinepath(ByVal ParamArray s() As String) As String
        Return combinepath(True, s)
    End Function

    Public Function trycombinepath(ByVal ParamArray s() As String) As String
        Return combinepath(False, s)
    End Function

    Public Function samepath(ByVal s1 As String, ByVal s2 As String) As Boolean
        s1 = fullpath(s1)
        s2 = fullpath(s2)
        Return strsame(s1, s2, False)
    End Function

    Private Function readfileImpl(ByVal fullpath As String, ByRef content As String) As Boolean
        Try
#If PocketPC OrElse Smartphone Then
            Dim sr As StreamReader = Nothing
            sr = File.OpenText(fullpath)
            content = sr.ReadToEnd()
            closereader(sr)
#Else
            content = File.ReadAllText(fullpath)
#End If
        Catch
            Return False
        End Try

        Return True
    End Function

    Private Function readfileImpl(ByVal fullpath As String, ByRef buff As bytes) As Boolean
        Try
#If PocketPC OrElse Smartphone Then
            Dim sr As StreamReader = Nothing
            sr = File.OpenText(fullpath)
            content = sr.ReadToEnd()
            closereader(sr)
#Else
            buff = New bytes(File.ReadAllBytes(fullpath))
#End If
        Catch
            Return False
        End Try

        Return True
    End Function

    Private Delegate Function readfileImplDelegate(Of T)(ByVal fullpath As String, ByRef o As t) As Boolean

    Private Function readfileImpl(Of T)(ByVal file As String, ByRef output As T, _
                                        ByVal r As readfileImplDelegate(Of T)) As Boolean
        assert(Not r Is Nothing)
        'since readfile will try several folders, so it does not make sense to raise error msg every where
        Dim i As Int64
        Dim s() As String = Nothing
        searchpaths(file, s)
        For i = 0 To s.Length() - 1
            If r(s(i), output) Then
                Return True
            End If
        Next

        Return False
    End Function

    Public Function readfile(ByVal file As String, ByRef content As String) As Boolean
        Return readfileImpl(file, content, AddressOf readfileImpl)
    End Function

    Public Function readfile(ByVal file As String, ByRef buff As bytes) As Boolean
        Return readfileImpl(file, buff, AddressOf readfileImpl)
    End Function

    Public Function writefile(ByVal file As String, ByVal content As String) As Boolean
        Try
#If PocketPC OrElse Smartphone Then
            Dim sw As IO.StreamWriter = Nothing
            sw = IO.File.CreateText(file)
            sw.Write(content)
            closewriter(sw)
#Else
            IO.File.WriteAllText(file, content)
#End If
            Return True
        Catch
            Return False
        End Try
    End Function

    Public Function appendfile(ByVal file As String, ByVal content As String) As Boolean
        Try
#If PocketPC OrElse Smartphone Then
            Dim sw As IO.StreamWriter = Nothing
            sw = IO.File.AppendText(file)
            sw.Write(content)
            closewriter(sw)
#Else
            IO.File.AppendAllText(file, content)
#End If
            Return True
        Catch
            Return False
        End Try
    End Function

    Public Function deletefile(ByVal filename As String) As Boolean
        Try
            File.Delete(filename)
        Catch
            Return False
        End Try
        Return True
    End Function

    Private Sub _uniqueappendpaths(ByRef ps As memblock(Of String), ByVal p As String)
        Dim i As Int64
        For i = 0 To ps.size() - 1
            If samepath(ps(i), p) Then
                Return
            End If
        Next
        'attention, +(a,b) operator in memblock will generate a new instance, instead of append to the left object
        'so need to byref here
        ps += p
    End Sub

    Public Sub searchpaths(ByVal base As String, ByRef ps As memblock(Of String))
        If ps Is Nothing Then
            ps = New memblock(Of String)()
        Else
            ps.clear()
        End If

        _uniqueappendpaths(ps, fullpath(base))
#If Not PocketPC AndAlso Not Smartphone Then
        _uniqueappendpaths(ps, combinepath(Environment.CurrentDirectory(), base))
        _uniqueappendpaths(ps, combinepath(envs.executingFullDirectory(), base))
#End If
    End Sub

    Public Sub searchpaths(ByVal base As String, ByRef paths() As String)
        Dim ps As memblock(Of String) = Nothing
        searchpaths(base, ps)
        ReDim paths(ps.size() - 1)
        Dim i As Int64
        For i = 0 To ps.size() - 1
            paths(i) = ps(i)
        Next
    End Sub

    Public Function filesize(ByVal filename As String, ByRef o As Int64) As Boolean
        If File.Exists(filename) Then
            Dim f As FileInfo = Nothing
            Try
                f = New FileInfo(filename)
            Catch
                Return False
            End Try

            o = f.Length()
            Return True
        Else
            Return False
        End If
    End Function

    Public Class unittests
        Public Shared Sub fullpath()
            Dim input As String = Nothing
            input = Console.ReadLine()
            While Not input Is Nothing
                Console.WriteLine(Global.fullpath(input))
                input = Console.ReadLine()
            End While
        End Sub
    End Class

End Module
